#include <errno.h>
#include <termios.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/tty.h>

#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>

extern int session_of_pgrp(int pgrp);
extern int tty_signal(int sig, struct tty_struct *tty);

static void flush(struct tty_queue * queue) {
    cli();
    queue->head = queue->tail;
    sti();
}

static void wait_until_sent(struct tty_struct * tty) {
}

static void send_break(struct tty_struct * tty) {
}

static int get_termios(struct tty_struct * tty, struct termios * termios) {
    int i;

    verify_area(termios, sizeof (*termios));
    for (i=0 ; i< (sizeof (*termios)) ; i++)
        put_fs_byte( ((char *)&tty->termios)[i] , i+(char *)termios );
    return 0;
}

static int set_termios(struct tty_struct * tty, struct termios * termios,
        int channel) {
    int i, retsig;

    if ((current->tty == channel) && (tty->pgrp != current->pgrp)) {
        retsig = tty_signal(SIGTTOU, tty);
        if (retsig == -ERESTARTSYS || retsig == -EINTR)
            return retsig;
    }

    for (i=0 ; i< (sizeof (*termios)) ; i++)
        ((char *)&tty->termios)[i]=get_fs_byte(i+(char *)termios);
    return 0;
}

static int get_termio(struct tty_struct * tty, struct termio * termio) {
    int i;
    struct termio tmp_termio;

    verify_area(termio, sizeof (*termio));
    tmp_termio.c_iflag = tty->termios.c_iflag;
    tmp_termio.c_oflag = tty->termios.c_oflag;
    tmp_termio.c_cflag = tty->termios.c_cflag;
    tmp_termio.c_lflag = tty->termios.c_lflag;
    tmp_termio.c_line = tty->termios.c_line;
    for(i=0 ; i < NCC ; i++)
        tmp_termio.c_cc[i] = tty->termios.c_cc[i];
    for (i=0 ; i< (sizeof (*termio)) ; i++)
        put_fs_byte( ((char *)&tmp_termio)[i] , i+(char *)termio );
    return 0;
}

static int set_termio(struct tty_struct * tty, struct termio * termio,
        int channel) {
    int i, retsig;
    struct termio tmp_termio;

    if ((current->tty == channel) && (tty->pgrp != current->pgrp)) {
        retsig = tty_signal(SIGTTOU, tty);
        if (retsig == -ERESTARTSYS || retsig == -EINTR)
            return retsig;
    }

    for (i=0 ; i< (sizeof (*termio)) ; i++)
        ((char *)&tmp_termio)[i]=get_fs_byte(i+(char *)termio);
    *(unsigned short *)&tty->termios.c_iflag = tmp_termio.c_iflag;
    *(unsigned short *)&tty->termios.c_oflag = tmp_termio.c_oflag;
    *(unsigned short *)&tty->termios.c_cflag = tmp_termio.c_cflag;
    *(unsigned short *)&tty->termios.c_lflag = tmp_termio.c_lflag;
    tty->termios.c_line = tmp_termio.c_line;
    for(i=0 ; i < NCC ; i++)
        tty->termios.c_cc[i] = tmp_termio.c_cc[i];
    return 0;
}

int tty_ioctl(int dev, int cmd, int arg) {
    struct tty_struct * tty;
    int     pgrp;

    if (MAJOR(dev) == 5) {
        dev=current->tty;
        if (dev<0)
            panic("tty_ioctl: dev<0");
    } else
        dev=MINOR(dev);

    tty = tty_table + dev;

    switch (cmd) {
        case TCGETS:
            return get_termios(tty,(struct termios *) arg);
        case TCSETSF:
            flush(&tty->read_q); /* fallthrough */
        case TCSETSW:
            wait_until_sent(tty); /* fallthrough */
        case TCSETS:
            return set_termios(tty,(struct termios *) arg, dev);
        case TCGETA:
            return get_termio(tty,(struct termio *) arg);
        case TCSETAF:
            flush(&tty->read_q); /* fallthrough */
        case TCSETAW:
            wait_until_sent(tty); /* fallthrough */
        case TCSETA:
            return set_termio(tty,(struct termio *) arg, dev);
        case TCSBRK:
            if (!arg) {
                wait_until_sent(tty);
                send_break(tty);
            }
            return 0;
        case TCXONC:
            switch (arg) {
                case TCOOFF:
                    tty->stopped = 1;
                    tty->write(tty);
                    return 0;
                case TCOON:
                    tty->stopped = 0;
                    tty->write(tty);
                    return 0;
                case TCIOFF:
                    if (STOP_CHAR(tty))
                        PUTCH(STOP_CHAR(tty),tty->write_q);
                    return 0;
                case TCION:
                    if (START_CHAR(tty))
                        PUTCH(START_CHAR(tty),tty->write_q);
                    return 0;
            }
            return -EINVAL;
        case TCFLSH:
            if (arg==0)
                flush(&tty->read_q);
            else if (arg==1)
                flush(&tty->write_q);
            else if (arg==2) {
                flush(&tty->read_q);
                flush(&tty->write_q);
            } else
                return -EINVAL;
        case TIOCGPGRP:
            verify_area((void *) arg,4);
            put_fs_long(tty->pgrp,(unsigned long *) arg);
            return 0;
        case TIOCSPGRP:
            if ((current->tty < 0) ||
                    (current->tty != dev) ||
                    (tty->session != current->session))
                return -ENOTTY;
            pgrp=get_fs_long((unsigned long *) arg);
            if (pgrp < 0)
                return -EINVAL;
            if (session_of_pgrp(pgrp) != current->session)
                return -EPERM;
            tty->pgrp = pgrp;
            return 0;
        case TIOCOUTQ:
            verify_area((void *) arg,4);
            put_fs_long(CHARS(tty->write_q),(unsigned long *) arg);
            return 0;
        case TIOCINQ:
            verify_area((void *) arg,4);
            put_fs_long(CHARS(tty->secondary),
                    (unsigned long *) arg);
            return 0;
        default:
            return -EINVAL;
    }
}

